package org.foreningsgatan.genetics.racers;

import static org.foreningsgatan.genetics.racers.Configuration.*;
import static org.foreningsgatan.genetics.racers.CommonMath.*;

import java.awt.geom.Point2D;

public class Car {
	private RacingSimulation simulation;
	
	protected Point2D position;
	
	protected Force self;
	protected Force turn;
	protected int fuel;
	protected int tick;
	
	protected boolean doRaiseSpeed;
	protected boolean doBreak;
	
	protected int targetCheckpointIndex;

	public Car(RacingSimulation simulation) {
		this.position = new Point2D.Double(0, 0);
		this.self = new Force(0, 0);
		this.turn = new Force(0, 0);
		this.doRaiseSpeed = false;
		this.doBreak = false;
		this.fuel = sInitialFuel;
		this.tick = 0;
		this.targetCheckpointIndex = 0;
		this.simulation = simulation;
	}
	
	public Force getSelf() {
		return self;
	}

	public void act() {
		if (doBreak) {
			self.strength -= sBreakingStrength;
		}
		
		self.strength -= sFriction;
		
		if (doRaiseSpeed) {
			self = Force.sum(self, 
					new Force(self.angle, sEngineStrength));
		}
		
		self.strength = limit(self.strength, 0, sTopSpeed);
		self.angle = wrap(self.angle + turn.angle, -Math.PI, Math.PI);
		
		position.setLocation(
				position.getX() + Math.cos(self.angle) * self.strength, 
				position.getY() + Math.sin(self.angle) * self.strength);
		
		// Pick up point?
		
		Point2D p = simulation.getCheckpoints().get(targetCheckpointIndex);
		
		if (position.distance(p) < sDistanceRequiredToCheckpoint) {
			targetCheckpointIndex++;
		}
		
		doRaiseSpeed = false;
		doBreak = false;
		
		turn.angle *= 0.75;
		
		fuel -= self.strength;
		tick++;
	}
	
	public void perform(Actions action) {
		switch (action) {
			case Break:
				doBreak = true;
				break;
			case HardTurnLeft:
				turn.angle += sHardTurn;
				break;
			case HardTurnRight:
				turn.angle -= sHardTurn;
				break;
			case SoftTurnLeft:
				turn.angle += sSoftTurn;
				break;
			case SoftTurnRight:
				turn.angle -= sSoftTurn;
				break;
			case ResetTurn:
				turn.angle = 0;
				break;
			case RaiseSpeed:
				doRaiseSpeed = true;
				break;
			case ReleaseBreak:
				doBreak = false;
				break;
			case ReleaseSpeed:
				doRaiseSpeed = false;
				break;
			case Idle:
		default:
			break;
		}
		
		turn.angle = limit(turn.angle, -sTurnLimit, sTurnLimit);
	}
	
	public float read(Sources sources) {
		Point2D p;
		
		switch (sources) {
			case Speed:
				return (float) (self.strength / sTopSpeed);
			case Angle:
				return (float) (self.angle / Math.PI) - 1.0f;
			case TurnAngle:
				return (float) (turn.angle / Math.PI) - 1.0f;
			case DistanceToNextCheckPoint:
				p = simulation.getCheckpoints().get(targetCheckpointIndex);
				
				double distance = position.distance(p);
				
				return (float) (distance / Configuration.sWidth);
			case AngleToNextCheckPoint:
				p = simulation.getCheckpoints().get(targetCheckpointIndex);
				
				Point2D ahead = new Point2D.Double(
						position.getX() + Math.cos(self.angle) * 10,
						position.getY() + Math.sin(self.angle) * 10
				);
				
				return (float) (angle(ahead, p, position) / Math.PI);
			case AngleBetweenCheckpoints:
				Point2D p1 = simulation.getCheckpoints().get(targetCheckpointIndex - 1);
				Point2D p2 = simulation.getCheckpoints().get(targetCheckpointIndex);
				Point2D p3 = simulation.getCheckpoints().get(targetCheckpointIndex + 1);
				
				return (float) (angle(p1, p3, p2) / Math.PI);
		}
		
		return 0.0f;
	}
	
	public Point2D getPosition() {
		return position;
	}
	
	public int getTargetCheckpointIndex() {
		return targetCheckpointIndex;
	}
}
